#include <algorithm/analytical_model/HeliostatPara.h>
#include <algorithm/analytical_model/ScenePara.h>
#include <algorithm/analytical_model/algorithm/ModelPara.h>
#include <algorithm/analytical_model/algorithm/PeakFluxParam.h>
#include <algorithm/analytical_model/algorithm/geometry/AllGeometry.h>
#include <algorithm/analytical_model/algorithm/geometry/Utils.h>
#include <algorithm/analytical_model/convolution/UNIZARPara.h>
#include <algorithm/shared/data/scene/Heliostat.h>
#include <algorithm/shared/data/scene/Receiver.h>
#include <algorithm/shared/data/scene/Sun.h>
#include <algorithm/shared/data/world/sun/sun_position/SunPosition.h>
#include <utils/ParaUtils.h>

using namespace solar;

template <>
void solar::computeSigma(SimulationPara* simulation_para, ScenePara* scene_para,
                         HeliostatPara* heliostat_para, CommonPara* common_para, Sun* sun,
                         Heliostat* heliostat, Receiver* receiver, UNIZARPara* ptr)
{
    constexpr double sigma_sun = 4.65 * 0.001;
    const auto dni = SOLAR_GET_VALUE(ScenePara, DNI_, *scene_para);
    const auto sun_position_ptr = SOLAR_GET_VALUE(Sun, sun_position_, *sun);
    const auto azimuth = SOLAR_GET_VALUE(SunPosition, azimuth_, *sun_position_ptr);
    const auto elevation = SOLAR_GET_VALUE(SunPosition, elevation_, *sun_position_ptr);
    const auto sun_direction_inv = angleToDirection(azimuth, elevation);
    const auto geometry_heliostat = SOLAR_GET_VALUE(Heliostat, geometry_, *heliostat);
    const auto geometry_receiver = SOLAR_GET_VALUE(Receiver, geometry_, *receiver);
    const auto sigma_tracking = SOLAR_GET_VALUE(HeliostatPara, sigma_tracking_, *heliostat_para);
    const auto sigma_slope_error =
        SOLAR_GET_VALUE(HeliostatPara, sigma_slope_error_, *heliostat_para);

    const auto heliostat_normal = ::solar::getNormal(geometry_heliostat.get());
    const auto heliostat_center = ::solar::getCenter(geometry_heliostat.get());
    const auto focus_center =
        ::solar::getFocusCenter(geometry_heliostat.get(), geometry_receiver.get());

    const auto cos_theta = std::abs(
        solar::dot(sun_direction_inv.get_unit_vector(), heliostat_normal.get_unit_vector()));
    const auto distance_heliostat_focus_center_pow_2 =
        (heliostat_center - focus_center).squared_length();
    const auto reflectivity = SOLAR_GET_VALUE(HeliostatPara, reflectivity_, *heliostat_para);
    const double peak_flux_param =
        peakFluxParam(ModelType::kConvolution, focus_center, heliostat_center, heliostat_normal, 0,
                      dni, reflectivity);
    const double sigma =
        std::sqrt((sigma_sun * sigma_sun +
                   2 * (1 + cos_theta * cos_theta) * sigma_slope_error.x() * sigma_slope_error.x() +
                   sigma_tracking.x() * sigma_tracking.x()) *
                  distance_heliostat_focus_center_pow_2);
    SOLAR_SET_VALUE(UNIZARPara, sigma_, sigma, *ptr)
    SOLAR_SET_VALUE(UNIZARPara, flux_param_, peak_flux_param, *ptr)
}
